package org.geektimes.projects.user.management;

import sun.rmi.transport.ObjectTable;

import javax.management.*;
import java.beans.BeanInfo;
import java.beans.MethodDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Created with IntelliJ IDEA.
 * Description:
 *
 * @Author: menglinggang
 * @Date: 2021-03-17
 * @Time: 9:23 上午
 */
public class ObjectShow2 implements DynamicMBean {

    private Description targetObject;

    private BeanInfo beanInfo;

    private final String description;

    /**
     * @serial The MBean qualified name.
     */
    private final String className;

    /**
     * @serial The MBean attribute descriptors.
     */
    private final MBeanAttributeInfo[] attributes;

    private final Map<String, MBeanAttributeInfo> attributesMap = new HashMap<>();

    /**
     * @serial The MBean operation descriptors.
     */
    private final MBeanOperationInfo[] operations;

    private final Map<String, List<MBeanOperationInfo>> operationsMap = new HashMap<>();

    /**
     * @serial The MBean constructor descriptors.
     */
    private final MBeanConstructorInfo[] constructors = new MBeanConstructorInfo[0];

    /**
     * @serial The MBean notification descriptors.
     */
    private final MBeanNotificationInfo[] notifications = new MBeanNotificationInfo[0];

    public ObjectShow2(Description targetObject, BeanInfo beanInfo) {
        this.beanInfo = beanInfo;
        description = targetObject.getDescription();
        className = targetObject.getClass().getName();
        attributes = parseMBeanAttributeInfos(beanInfo);
        operations = parseMBeanOperationInfos(targetObject);
        Map<String, MBeanAttributeInfo> attributesCollect = Arrays.stream(attributes).collect(Collectors.toMap(MBeanAttributeInfo::getName, Function.identity()));
        attributesMap.putAll(attributesCollect);

        Map<String, List<MBeanOperationInfo>> operationInfoMap = Arrays.stream(operations).collect(Collectors.groupingBy(MBeanOperationInfo :: getName));
        operationsMap.putAll(operationInfoMap);
    }

    private MBeanOperationInfo[] parseMBeanOperationInfos(Description targetObject) {
        Method[] declaredMethods = targetObject.getClass().getDeclaredMethods();
        if (declaredMethods == null || declaredMethods.length == 0){
            return new MBeanOperationInfo[0];
        }
        MBeanOperationInfo[] target = new MBeanOperationInfo[declaredMethods.length];
        for (int i = 0; i < declaredMethods.length; i++) {
            Method declaredMethod = declaredMethods[i];
            MBeanOperationInfo mBeanOperationInfo = new MBeanOperationInfo(declaredMethod.getName(), declaredMethod);
            target[i] = mBeanOperationInfo;
        }
        return target;
    }

    private MBeanAttributeInfo[] parseMBeanAttributeInfos(BeanInfo beanInfo) {
        try {
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            if (propertyDescriptors.length == 0) {
                return new MBeanAttributeInfo[0];
            }

            MBeanAttributeInfo[] target = new MBeanAttributeInfo[propertyDescriptors.length];
            for (int i = 0; i < propertyDescriptors.length; i++) {
                PropertyDescriptor descriptor = propertyDescriptors[i];
                MBeanAttributeInfo attributeInfo = new MBeanAttributeInfo(descriptor.getName(), descriptor.getShortDescription(), descriptor.getReadMethod(), descriptor.getWriteMethod());
                target[i] = attributeInfo;
            }
        } catch (javax.management.IntrospectionException e) {
        }
        return new MBeanAttributeInfo[0];
    }


    @Override
    public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
        MBeanAttributeInfo attributeInfo = attributesMap.get(attribute);
        if (attributeInfo == null) {
            throw new AttributeNotFoundException();
        }
        if (!attributeInfo.isReadable() && !attributeInfo.isIs()){
            throw new AttributeNotFoundException();
        }
        try {
            Field declaredField = targetObject.getClass().getDeclaredField(attribute);
            if (!declaredField.isAccessible()) {
                declaredField.setAccessible(true);
            }
            return declaredField.get(targetObject);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new ReflectionException(e, attribute + " has error");
        }
    }

    @Override
    public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
        MBeanAttributeInfo attributeInfo = attributesMap.get(attribute.getName());
        if (attributeInfo == null) {
            throw new AttributeNotFoundException();
        }
        if (!attributeInfo.isReadable()){
            throw new AttributeNotFoundException();
        }
        try {
            Field declaredField = targetObject.getClass().getDeclaredField(attribute.getName());
            if (!declaredField.isAccessible()) {
                declaredField.setAccessible(true);
            }
            declaredField.set(targetObject, attribute.getValue());
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new ReflectionException(e, attribute + " has error");
        }

    }

    @Override
    public AttributeList getAttributes(String[] attributes) {
        AttributeList list = new AttributeList();
        if (attributes == null || attributes.length == 0) {
            return list;
        }
        Arrays.stream(attributes).forEach(attribute -> {
            try {
                Attribute resultItem = new Attribute(attribute, getAttribute(attribute));
                list.add(resultItem);
            } catch (AttributeNotFoundException e) {
            } catch (MBeanException e) {
            } catch (ReflectionException e) {
            }

        });
        return list;
    }

    @Override
    public AttributeList setAttributes(AttributeList attributes) {
        AttributeList objects = new AttributeList();
        if (attributes == null || attributes.size() == 0) {
            return objects;
        }
        attributes.asList().stream().forEach(item ->{
            try {
                setAttribute(item);
                objects.add(item);
            } catch (AttributeNotFoundException e) {
            } catch (InvalidAttributeValueException e) {
            } catch (MBeanException e) {
            } catch (ReflectionException e) {
            }
        });
        return null;
    }

    @Override
    public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {
        return null;
    }

    @Override
    public MBeanInfo getMBeanInfo() {
        return new MBeanInfo(className, description, attributes, constructors, operations, notifications);
    }
}
